---
title: Скрипты и обработка событий
description: >-
  Как добавить интерактивность на стороне клиента в компоненты Astro, используя нативный JavaScript API
i18nReady: true
---
import ReadMore from '~/components/ReadMore.astro'

Вы можете добавить интерактивность в компоненты Astro без [использования UI-фреймворка](/ru/guides/framework-components/), такого как React, Svelte, Vue и т. д., используя стандартный HTML тег `<script>`. Это позволяет отправлять JavaScript в браузер и добавлять функциональность в компоненты Astro.

## Клиентские скрипты

Скрипты могут использоваться для того, чтобы добавить обработчики событий, отправить аналитические данные, анимировать элементы и многого другого, что JavaScript может делать в Интернете.

```astro
<!-- src/components/ConfettiButton.astro -->
<button data-confetti-button>Celebrate!</button>

<script>
  // Импорт модулей npm.
  import confetti from 'canvas-confetti';

  // Находим наш компонент DOM на странице.
  const buttons = document.querySelectorAll('[data-confetti-button]');

  // Добавляем обработчики событий, чтобы запускать конфетти при нажатии на кнопку.
  buttons.forEach((button) => {
    button.addEventListener('click', () => confetti());
  });
</script>
```

По умолчанию Astro обрабатывает и собирает теги `<script>`, добавляя поддержку импорта модулей npm, написания TypeScript и т. д.

## Использование `<script>` в Astro

В файлы `.astro` можно добавить клиентский JavaScript, добавив один (или несколько) тегов `<script>`.

В этом примере добавление компонента `<Hello />` на страницу приведет к выводу сообщения в консоль браузера.

```astro title="src/components/Hello.astro"
<h1>Welcome, world!</h1>

<script>
  console.log('Welcome, browser console!');
</script>
```

### Обработка скриптов

По умолчанию теги `<script>` обрабатываются Astro.

- Любые импорты будут скомпонованы, что позволит вам импортировать локальные файлы или модули Node.
- Обработанный скрипт будет вставлен в `<head>` вашей страницы с помощью [`type="module"`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules).
- TypeScript поддерживается полностью, включая импорт файлов TypeScript.
- Если ваш компонент используется на странице несколько раз, скрипт будет добавлен в сборку и на страницу только единожды.

```astro title="src/components/Example.astro"
<script>
  // Обработан! Собран! Поддерживает TypeScript!
  // Импорт локальных скриптов и модулей Node работает.
</script>
```

Атрибут `type="module"` заставляет браузер воспринимать скрипт как модуль JavaScript. Это дает несколько преимуществ в плане производительности:
- Рендеринг не блокируется. Браузер продолжает обрабатывать остальную часть HTML, пока загружается скрипт модуля и его зависимости.
- Браузер ожидает обработки HTML перед выполнением модульных скриптов. Вам не нужно прослушивать событие "load".
- Атрибуты `async` и `defer` не нужны. Модульные скрипты всегда откладываются.

:::note
Атрибут `async` полезен для обычных скриптов, поскольку он не позволяет им блокировать рендеринг. Однако модульные скрипты уже имеют такое поведение. Добавление `async` к модульному скрипту приведет к тому, что он будет выполняться до полной загрузки страницы. Вероятно, это не то, что вам нужно.
:::

### Отказ от обработки

Чтобы запретить Astro обрабатывать скрипт, добавьте директиву `is:inline`.

```astro title="src/components/InlineScript.astro" "is:inline"
<script is:inline>
  // Будет выведено в HTML именно так, как написано!
  // Локальные импорты не разрешены и не будут работать.
  // Если в компоненте, то повторяется каждый раз, когда компонент используется.
</script>
```

:::note
В некоторых ситуациях Astro не будет обрабатывать теги скриптов. В частности, добавление `type="module"` или любого другого атрибута, кроме `src`, к тегу `<script>` приведет к тому, что Astro будет обрабатывать тег так, как если бы он содержал директиву `is:inline`. То же самое будет справедливо, если скрипт записан в выражении JSX.
:::

<ReadMore>Подробнее о доступных директивах на тегах `<script>` смотрите на странице [справочника директив](/ru/reference/directives-reference/#script--style-directives).</ReadMore>


### Включение файлов javascript на страницу

Возможно, вы захотите написать свои скрипты в виде отдельных файлов `.js`/`.ts` или вам понадобится сослаться на внешний скрипт на другом сервере. Вы можете сделать это, сославшись на них в атрибуте `<script>` тега ``src`.

#### Импорт локальных скриптов

**Когда это использовать:** когда ваш скрипт находится внутри `src/`.

Astro создаст, оптимизирует и добавит эти скрипты на страницу за вас, следуя своим [правилам обработки скриптов](#обработка-скриптов).

```astro title="src/components/LocalScripts.astro"
<!-- относительный путь к скрипту в `src/scripts/local.js` -->
<script src="../scripts/local.js"></script>

<!-- также работает для локальных файлов TypeScript -->
<script src="./script-with-types.ts"></script>
```

#### Загрузка внешних скриптов

**Когда использовать:** когда ваш JavaScript-файл находится внутри `public/` или на CDN.

Чтобы загрузить скрипты вне папки `src/` вашего проекта, включите директиву `is:inline`. Этот подход позволяет обойтись без обработки, сборки и оптимизации JavaScript, которые обеспечивает Astro, когда вы импортируете скрипты, как описано выше.

```astro title="src/components/ExternalScripts.astro" "is:inline"
<!-- абсолютный путь к скрипту по адресу `public/my-script.js` -->
<script is:inline src="/my-script.js"></script>

<!-- полный URL к скрипту на удаленном сервере -->
<script is:inline src="https://my-analytics.com/script.js"></script>
```

## Общие шаблоны скриптов

### Обработка `onclick` и других событий

Некоторые UI-фреймворки используют собственный синтаксис для обработки событий, например `onClick={...}` (React/Preact) или `@click="..."` (Vue). Astro больше следует стандартному HTML и не использует пользовательский синтаксис для событий.

Вместо этого вы можете использовать [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) в теге `<script>` для обработки взаимодействия с пользователем.

```astro title="src/components/AlertButton.astro"
<button class="alert">Click me!</button>

<script>
  // Находит все кнопки с классом `alert` на странице.
  const buttons = document.querySelectorAll('button.alert');

  // Обрабатывает клик на всех кнопках.
  buttons.forEach((button) => {
    button.addEventListener('click', () => {
      alert('Button was clicked!');
    });
  });
</script>
```

:::note
Если на странице имеется несколько компонентов `<AlertButton />`, Astro не будет запускать скрипт несколько раз. Скрипты собираются и добавляются на страницу единожды. Использование `querySelectorAll` гарантирует, что скрипт добавит обработчики событий к каждой кнопке с классом `alert`, найденной на странице.
:::

### Веб-компоненты с пользовательскими элементами

Вы можете создавать собственные HTML-элементы с пользовательским поведением, используя стандарт Web Components. Определение [пользовательского элемента](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) в компоненте `.astro` позволяет создавать интерактивные компоненты без использования библиотеки UI-фреймворка.

В этом примере мы определяем новый HTML-элемент `<astro-heart>`, который отслеживает количество нажатий на кнопку сердца и обновляет `<span>` актуальным значением. 

```astro title="src/components/AstroHeart.astro"
<!-- Оборачиваем компонент в наш пользовательский элемент "astro-heart". -->
<astro-heart>
  <button aria-label="Heart">💜</button> × <span>0</span>
</astro-heart>

<script>
  // Определяем поведение для нашего нового типа HTML-элемента.
  class AstroHeart extends HTMLElement {
    constructor() {
			super();
      let count = 0;

      const heartButton = this.querySelector('button');
      const countSpan = this.querySelector('span');

      // При каждом нажатии на кнопку обновляем счетчик.
			heartButton.addEventListener('click', () => {
        count++;
        countSpan.textContent = count.toString();
      });
		}
  }

  // Сообщаем браузеру, чтобы он использовал наш класс AstroHeart для элементов <astro-heart>.
  customElements.define('astro-heart', AstroHeart);
</script>
```

Использование пользовательского элемента здесь имеет два преимущества:

1. Вместо поиска по всей странице с помощью `document.querySelector()`, вы можете использовать `this.querySelector()`, который ищет только в пределах текущего экземпляра пользовательского элемента. Это позволяет упростить работу с дочерними элементами внутри элемента. 

2. Хотя `<script>` выполняется только один раз, браузер будет запускать метод `constructor()` нашего пользовательского элемента каждый раз, когда найдет на странице `<astro-heart>`. Это означает, что вы можете смело писать код для одного компонента, даже если собираетесь использовать этот компонент несколько раз на странице.

<ReadMore>Подробнее о пользовательских элементах вы можете узнать из [руководства по многоразовым веб-компонентам web.dev](https://web.dev/custom-elements-v1/) и [введения в пользовательские элементы MDN](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements).</ReadMore>


### Передача переменных frontmatter в скрипты

В компонентах Astro код в [frontmatter](/ru/basics/astro-components/#скрипт-компонента) между кодовым забором `---` выполняется на сервере и недоступен в браузере. Чтобы передавать переменные с сервера на клиент, нам нужен способ хранить наши переменные и затем считывать их при запуске JavaScript в браузере.

Один из способов сделать это - использовать [атрибуты `data-*`](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes) для хранения значений переменных в HTML-выводах. Скрипты, включая пользовательские элементы, могут считывать эти атрибуты с помощью свойства `dataset` элемента, когда HTML загружается в браузере.

В этом примере компонента свойство `message` хранится в атрибуте `data-message`, поэтому пользовательский элемент может прочитать `this.dataset.message` и получить значение этого свойства в браузере.

```astro title="src/components/AstroGreet.astro" {2} /data-message={.+}/ "this.dataset.message"
---
const { message = 'Welcome, world!' } = Astro.props;
---

<!-- Храним реквизит сообщения как атрибут данных. -->
<astro-greet data-message={message}>
  <button>Say hi!</button>
</astro-greet>

<script>
  class AstroGreet extends HTMLElement {
    constructor() {
			super();

      // Считываем сообщение из атрибута данных.
      const message = this.dataset.message;
      const button = this.querySelector('button');
      button.addEventListener('click', () => {
        alert(message);
      });
		}
  }

  customElements.define('astro-greet', AstroGreet);
</script>
```

Теперь мы можем использовать наш компонент несколько раз, и каждый раз нас будет приветствовать разное сообщение.

```astro title="src/pages/example.astro"
---
import AstroGreet from '../components/AstroGreet.astro';
---

<!-- Используйте сообщение по умолчанию: “Welcome, world!” -->
<AstroGreet />

<!-- Использовать пользовательские сообщения, передаваемые в качестве пропса. -->
<AstroGreet message="Lovely day to build components!" />
<AstroGreet message="Glad you made it! 👋" />
```

:::tip[Знаете ли вы?]
Это как раз то, что Astro делает под капотом, когда вы передаете пропсы компоненту, написанному с использованием UI-фреймворка, например React! Для компонентов с директивой `client:*` Astro создаёт кастомный элемент `<astro-island>` с атрибутом `props`, который и хранит все серверные данные в HTML.
:::